#pragma once

#include <vector>

#include "../Datasets/Dataset.h"

#include "Fitness/Additive/Purity.h"
#include "Fitness/Additive/AdditiveFitnessFunction.h"
#include "DistanceFunctions/DistanceFunction.h"
#include "Neighbourhood/Neighbourhood.h"
#include "DistanceFunctions/ProximitySearch.h"
#include "omp.h"

namespace CLEVER {
namespace RegionDiscovery {

//! A utility class to benchmark the sample application.
class CStopWatch
{
public:
	//CStopWatch() { QueryPerformanceFrequency(&PerfFreq); }
	CStopWatch() {}
	//void Start() { start.QuadPart = 0; QueryPerformanceCounter(&start); }
	void Start() { clock_gettime(CLOCK_REALTIME, &start); }     //need to add rt library to the linker
	//void Stop() { end.QuadPart = 0; QueryPerformanceCounter(&end); }
	void Stop() { clock_gettime(CLOCK_REALTIME, &end); }        //need to add rt library to the linker
	/*long double GetDuration() {
	        long double duration=(end.QuadPart - start.QuadPart) / long double(PerfFreq.QuadPart);
	        return  duration;
	    }*/
	long double GetDuration()
	{
		timespec temp;

		if ((end.tv_nsec - start.tv_nsec) < 0) {
			temp.tv_sec = end.tv_sec - start.tv_sec - 1;
			temp.tv_nsec = 1000000000 + end.tv_nsec - start.tv_nsec; //a nanosecond is 10E-9 second
		} else {
			temp.tv_sec = end.tv_sec - start.tv_sec;
			temp.tv_nsec = end.tv_nsec - start.tv_nsec;
		}

		long double duration = temp.tv_sec + (temp.tv_nsec / 1000000000.0); //in seconds, resolution up to nanosecond
		return duration;
	}


private:
	timespec start, end, PerfFreq;
};

class Clever
{
public:
	struct RunInfo
	{
		unsigned long iterations;
		int k;
		double duration;
		double fitness;
		int replacements;

		RunInfo() : iterations(0), k(0), duration(0), fitness(0), replacements(0)
		{ }
	};

	//! Constructor
	/*!
	        \param _att_dataset Dataset
	        \param _att Cluster attribute index
	        \param _fit Fitness function
	        \param _search Search function
	        \param neighbourhood_def Neighbourhood
	        \param p Sample size
	        \param q Resampling factor
	 */
	Clever(NumericDataset * _att_dataset,
		int _att,
		Fitness::Additive::AdditiveFitnessFunction<double> * _fit,
		DistanceFunctions::ProximitySearch<double> * _search,
		RegionDiscovery::DistanceFunctions::DistanceFunction<double> * distfunc,
		Neighbourhood * neighbourhood_def,
		int p, int q, int useDistanceMatrix, int init_k, int rIsLoopPara, double rMaxRuntime, int rMaxRplcmnt, int rIncreUpdate);

	Clever(DoubleArrayDataset * _att_dataset,
		int _att,
		Fitness::Additive::AdditiveFitnessFunction<double> * _fit,
		DistanceFunctions::ProximitySearch<double> * _search,
		DistanceFunctions::DistanceFunction<double> * distfunc,
		Neighbourhood * neighbourhood_def,
		int p, int q, int useDistanceMatrix, int init_k, int rIsLoopPara, double rMaxRuntime, int rMaxRplcmnt, int rIncreUpdate);
	~Clever();

	//! Calculates the fitness of the dataset with the fitness function
	double FindFitness(Fitness::Additive::AdditiveFitnessFunction<double> * _fit, NumericDataset * data, NumericDataset * repSet);

	double FindFitness(Fitness::Additive::AdditiveFitnessFunction<double> * _fit, DoubleArrayDataset * data, NumericDataset * repSet);

	double FindFitness(Fitness::Additive::AdditiveFitnessFunction<double> * _fit, std::vector<double> & data, NumericDataset * gData, NumericDataset * repSet);

	//!Updates the keys in attr to match the size k
	void UpdateNumOfRegions(Attribute & attr, int k);

	NumericDataset * FindRegions(NumericDataset * rep_set, std::vector<int> & cur);

	void FindRegions(NumericDataset * rep_set, std::vector<int> & cur, NumericDataset * data);

	void FindRegions(NumericDataset * rep_set, std::vector<int> & cur, std::vector<double> & data);

	void FindRegions(NumericDataset * rep_set, std::vector<int> & cur, DoubleArrayDataset * data);

	NumericDataset * FindRegionsIterative(NumericDataset * rep_set, std::vector<int> & cur, std::vector<int> & prev);

	void FindRegionsIterative(NumericDataset * rep_set, std::vector<int> & cur, std::vector<int> & prev, std::vector<double> & data);

	//! Creates a dataset with the specified reps
	NumericDataset * CreateRepDataset(std::vector<int> rReps);

	std::vector<int> GetNonReps(std::vector<int> & rReps, int rSize);

	std::vector<int> GenerateInitialReps();

	std::vector<int> FindBestNeighbour(std::vector<std::vector<int> > neighbours, double rFitness, int rIsLoopPara, RunInfo * rinfo = 0);

	std::vector<int> FindBestNeighbour(std::vector<std::vector<int> > neighbours, double rFitness, RunInfo * rinfo = 0);

	NumericDataset * FinalReps;
	std::vector<int> FinalIndices;
	double FinalFitness;

	//! CLEVER's main function.
	void Run(CStopWatch * rC, RunInfo * info = 0);

protected:
	void RandomizedSPAMAlgorithm(CStopWatch * rC, RunInfo * rinfo);

	int k, p, q, useDistanceMatrix, useDoubleArrayDataset, IsLoopPara, _increUpdate;

	NumericDataset * _att_dataset;
	DoubleArrayDataset * _att_datasetDouble;
	//std::vector<NumericDataset* > vlocal_dataset;
	std::vector<DoubleArrayDataset * > g_column_datasetDoubleArray;
	std::vector< std::vector<double> > g_column_dataset;

	int _att;
	Fitness::Additive::AdditiveFitnessFunction<double> * _fit;
	std::vector<Fitness::Additive::AdditiveFitnessFunction<double> *> vlocal_fit;
	DistanceFunctions::ProximitySearch<double> * _search;
	Neighbourhood * neighbourhood_def;
	RegionDiscovery::DistanceFunctions::DistanceFunction<double> * distfunc;
	double _MaxRunTimeAllowed; // in seconds
	int _MaxSolutionReplacementAllowed;
};
}
}
